import "../../lib/ramda.js"
import utils from "../../lib/utils.js"

const Consts = {
    // 五子棋规则只有15路棋盘
    BOARD_SIZE:     15,
    BOARD_LEN:      225,
    LOCATION_MIN:   0,
    LOCATION_MAX:   14,
    // 棋子常量表示
    P_BLACK:-1,
    P_EMPTY:0,
    P_WHITE:1,
    P_swap(flag){
        return (-flag)
    },
    P_name(flag){
        if (flag == Consts.P_BLACK) {return "黑棋"}
        if (flag == Consts.P_WHITE) {return "白棋"}
        return "空位"
    },
    to_i: (x,y)=>(y*Consts.BOARD_SIZE + x),
    to_x: (i)=>(i % Consts.BOARD_SIZE),
    to_y: (i)=>(Math.floor(i / Consts.BOARD_SIZE)),
    text_xy: (x, y)=>(`${String.fromCodePoint('A'.codePointAt(0) + x)}${Consts.BOARD_SIZE - y}`),
    text_i: (i)=>(Consts.text_xy(Consts.to_x(i), Consts.to_y(i))),
    // 星位
    STARS:[
        [7,7],      // 天元
        [3,3],      // 左上
        [11,3],     // 右上
        [3,11],     // 左下
        [11,11],    // 右下
    ],
    // 位置表示
    LOC_TEXTS:[
        "A15","B15","C15","D15","E15","F15","G15","H15","I15","J15","K15","L15","M15","N15","O15",
        "A14","B14","C14","D14","E14","F14","G14","H14","I14","J14","K14","L14","M14","N14","O14",
        "A13","B13","C13","D13","E13","F13","G13","H13","I13","J13","K13","L13","M13","N13","O13",
        "A12","B12","C12","D12","E12","F12","G12","H12","I12","J12","K12","L12","M12","N12","O12",
        "A11","B11","C11","D11","E11","F11","G11","H11","I11","J11","K11","L11","M11","N11","O11",
        "A10","B10","C10","D10","E10","F10","G10","H10","I10","J10","K10","L10","M10","N10","O10",
        "A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9", "I9", "J9", "K9", "L9", "M9", "N9", "O9",
        "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8", "J8", "K8", "L8", "M8", "N8", "O8",
        "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "I7", "J7", "K7", "L7", "M7", "N7", "O7",
        "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "I6", "J6", "K6", "L6", "M6", "N6", "O6",
        "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "I5", "J5", "K5", "L5", "M5", "N5", "O5",
        "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4", "J4", "K4", "L4", "M4", "N4", "O4",
        "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "I3", "J3", "K3", "L3", "M3", "N3", "O3",
        "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2", "J2", "K2", "L2", "M2", "N2", "O2",
        "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1", "K1", "L1", "M1", "N1", "O1"],
    // 棋形表示
    D1:1,           // 死一     bab---
    D2:2,           // 死二     baab--
    D3:3,           // 死三     baaab-
    D4:4,           // 死四     baaaab
    S1:8,           // 眠一     ba----
    L1:16,          // 活一     -a----
    S2:32,          // 眠二     baa---
    L2:48,          // 活二     -aa---
    S3:64,          // 眠三     baaa--
    L3:128,         // 活三     -aaa--
    S4:256,         // 冲四     baaaa-
    S44:1024,       // 双冲四   a-aaa-a
    L4:16384,       // 活四     -aaaa-
    UR:65536,       // 成五     -aaaaa
    SSR:65535,      // 长连     aaaaaa
    SHAPE_NAME: undefined,
    // 超过总时限后的单步秒数
    MAXTIME_STEP_FOR_OVERTOTAL:10,
}
Consts.SHAPE_NAME = {
    [Consts.D1]:"死一",
    [Consts.D2]:"死二",
    [Consts.D3]:"死三",
    [Consts.D4]:"死四",
    [Consts.S1]:"眠一",
    [Consts.S2]:"眠二",
    [Consts.S3]:"眠三",
    [Consts.S4]:"冲四",
    [Consts.S44]:"双冲四",
    [Consts.L1]:"活一",
    [Consts.L2]:"活二",
    [Consts.L3]:"活三",
    [Consts.L4]:"活四",
    [Consts.UR]:"成五",
    [Consts.SSR]:"长连",
}

const Core = {
    board_make(from){
        return new Int8Array(R.is(Int8Array,from) ? from : Consts.BOARD_LEN)
    },
    board_clear(me){
        me.fill(Consts.P_EMPTY)
    },
    board_get(me, i){
        return me[i]
    },
    board_set(me, i, pflag){
        me[i] = pflag
    },
    board_for_pieces(me, f){
        for (let i = 0; i < me.length; i++){
            f(me[i], i, Consts.to_x(i), Consts.to_y(i))
        }
    },
    board_sum_piece(me, pflag){
        let sum = 0
        for (let i = 0; i < me.length; i++){
            if (me[i] == pflag){sum++}
        }
        return sum
    },
    way_up(i){
        return ((i > Consts.LOCATION_MAX) ? (i - Consts.BOARD_SIZE) : -1)
    },
    way_down(i){
        const j = i + Consts.BOARD_SIZE
        return ((j < Consts.BOARD_LEN) ? j : -1)
    },
    way_left(i){
        return (((i % Consts.BOARD_SIZE) > 0) ? (i - 1) : -1)
    },
    way_right(i){
        return (((i % Consts.BOARD_SIZE) < Consts.LOCATION_MAX) ? (i + 1) : -1)
    },
    way_lu(i){
        const x = Consts.to_x(i)
        const y = Consts.to_y(i)
        return (((x > 0) && (y > 0)) ? (Consts.to_i(x-1, y-1)) : -1)
    },
    way_ld(i){
        const x = Consts.to_x(i)
        const y = Consts.to_y(i)
        return (((x > 0) && (y < Consts.LOCATION_MAX)) ? (Consts.to_i(x-1, y+1)) : -1)
    },
    way_ru(i){
        const x = Consts.to_x(i)
        const y = Consts.to_y(i)
        return (((x < Consts.LOCATION_MAX) && (y > 0)) ? (Consts.to_i(x+1, y-1)) : -1)
    },
    way_rd(i){
        const x = Consts.to_x(i)
        const y = Consts.to_y(i)
        return (((x < Consts.LOCATION_MAX) && (y < Consts.LOCATION_MAX)) ? (Consts.to_i(x+1, y+1)) : -1)
    },
    // 棋形模式串，没有死一因为不匹配时默认死一
    _LINE_STR_TABS:{
        // 活一
        "-a----":Consts.L1,
        "-a---b":Consts.L1,
        "--a---":Consts.L1,
        "--a--b":Consts.L1,
        "b-a---":Consts.L1,
        "---a--":Consts.L1,
        "---a-b":Consts.L1,
        "b--a--":Consts.L1,
        "----a-":Consts.L1,
        "b---a-":Consts.L1,
        // 眠一
        "----ab":Consts.S1,
        "ba----":Consts.S1,
        // 活二
        "-aa---":Consts.L2,
        "-aa--b":Consts.L2,
        "--aa--":Consts.L2,
        "--aa-b":Consts.L2,
        "b-aa--":Consts.L2,
        "--a-a-":Consts.L2,
        "--a-a-":Consts.L2,
        "-a-a--":Consts.L2,
        "-a-a-b":Consts.L2,
        "-a--a-":Consts.L2,
        // 眠二
        "---aab":Consts.S2,
        "--a-ab":Consts.S2,
        "-a--ab":Consts.S2,
        "a---ab":Consts.S2,
        "baa---":Consts.S2,
        "ba-a--":Consts.S2,
        "ba--a-":Consts.S2,
        "ba---a":Consts.S2,
        "-a---a":Consts.S2,
        // 死二
        "-baab-":Consts.D2,
        "-baabb":Consts.D2,
        "-baaba":Consts.D2,
        "abaab-":Consts.D2,
        "abaaba":Consts.D2,
        "abaabb":Consts.D2,
        "bbaabb":Consts.D2,
        "bbaa-b":Consts.D2,
        "b-aa-b":Consts.D2,
        "baa--b":Consts.D2,
        "ba-a-b":Consts.D2,
        "ba--ab":Consts.D2,
        "b-a-ab":Consts.D2,
        "b--aab":Consts.D2,
        "baa-b-":Consts.D2,
        "baa-bb":Consts.D2,
        "baa-ba":Consts.D2,
        "ba-ab-":Consts.D2,
        "ba-abb":Consts.D2,
        "ba-aba":Consts.D2,
        "b-aab-":Consts.D2,
        "b-aabb":Consts.D2,
        "b-aaba":Consts.D2,
        // 活三
        "-aaa--":Consts.L3,
        "--aaa-":Consts.L3,
        "-aa-a-":Consts.L3,
        "-a-aa-":Consts.L3,
        // 眠三
        "--aaab":Consts.S3,
        "-a-aab":Consts.S3,
        "a--aab":Consts.S3,
        "aa--ab":Consts.S3,
        "-aa-ab":Consts.S3,
        "baaa--":Consts.S3,
        "baa-a-":Consts.S3,
        "baa--a":Consts.S3,
        "ba--aa":Consts.S3,
        "ba-aa-":Consts.S3,
        "-a-a-a":Consts.S3,
        "ba-a-a":Consts.S3,
        "-a--aa":Consts.S3,
        "-aa--a":Consts.S3,
        "aa--a-":Consts.S3,
        "a--aa-":Consts.S3,
        // 死三
        "baaab-":Consts.D3,
        "baaabb":Consts.D3,
        "baaaba":Consts.D3,
        "b-aaab":Consts.D3,
        "ba-aab":Consts.D3,
        "baa-ab":Consts.D3,
        "baaa-b":Consts.D3,
        // 活四
        "-aaaa-":Consts.L4,
        // 冲四
        "-aaaab":Consts.S4,
        "baaaa-":Consts.S4,
        "aaa-a-":Consts.S4,
        "aaa-ab":Consts.S4,
        "aa-aa-":Consts.S4,
        "aa-aab":Consts.S4,
        "a-aaa-":Consts.S4,
        "a-aaab":Consts.S4,
        // 死四
        "baaaab":Consts.D4,
        // 成五
        "-aaaaa":Consts.UR,
        "baaaaa":Consts.UR,
        "aaaaa-":Consts.UR,
        "aaaaab":Consts.UR,
        // 长连
        "aaaaaa":Consts.SSR,
    },
    LINE_STR_LEN:6,
    // 将一条线以i为中心分析棋形
    line(board, pflag_me, loc, way_a, way_b){
        // 以i点为基点，展开从i点在开头至末尾的6个六元组
        const tuples = [
            ['b','b','b','b','b','b'],
            ['b','b','b','b','b','b'],
            ['b','b','b','b','b','b'],
            ['b','b','b','b','b','b'],
            ['b','b','b','b','b','b'],
            ['b','b','b','b','b','b'],
        ]
        let a = loc
        for (let x = 0; x < Core.LINE_STR_LEN; x++){
            if (a < 0){break}
            let b = a
            for (let y = 0; y < Core.LINE_STR_LEN; y++){
                if (b < 0){break}
                const pflag = ((b == loc) ? pflag_me : Core.board_get(board, b))
                if (pflag == pflag_me){tuples[x][y] = 'a'}
                else if (pflag == Consts.P_EMPTY){tuples[x][y] = '-'}
                b = way_b(b)
            }
            a = way_a(a)
        }
        // 特殊棋形计数
        let count_s4 = 0
        // 每个六元组对应的模式
        const vals = [Consts.D1, Consts.D1, Consts.D1, Consts.D1, Consts.D1, Consts.D1]
        for (let i in vals){
            const str = tuples[i].join('')
            // 仅在匹配到模式时分析此六元组
            const val = this._LINE_STR_TABS[str]
            if (! R.isNil(val)){
                vals[i] = val
                // 对特殊棋形的分析(双冲四/长连)
                if (val == Consts.S4){
                    count_s4++
                    if (count_s4 > 1){return Consts.S44}
                }else if (val == Consts.SSR){return Consts.SSR}
            }
        }
        return Math.max(...vals)
    },
    // 计算一个点的4条线的棋形
    point(board, i, pflag){
        if (pflag == Consts.P_EMPTY){return [0, 0, 0, 0]}
        return [
            Core.line(board, pflag, i, Core.way_left, Core.way_right),  // 横线
            Core.line(board, pflag, i, Core.way_up, Core.way_down),     // 纵线
            Core.line(board, pflag, i, Core.way_ld, Core.way_ru),       // 左斜
            Core.line(board, pflag, i, Core.way_lu, Core.way_rd),       // 右斜
        ]
    },
}

// 基本规则
const RuleBase = {
    INVALID_MOVE:0,         // 无效走棋(仅指非空位)
    NORMAL_MOVE:1,          // 正常走棋
    FORBIDDEN_MOVE:2,       // 禁手
    WINNER_MOVE:3,          // 胜着(仅指连五)
    select_nums: R.always(0),
    can_swap: R.always(false),
    for_point(board, loc, pflag){
        if (Core.board_get(board, loc) != Consts.P_EMPTY){return RuleBase.INVALID_MOVE}
        const lines = Core.point(board, loc, pflag)
        return  ((this.use_forbidden && (pflag == Consts.P_BLACK)) ? RuleBase.forbidden(lines) : RuleBase.white(lines))
    },
    forbidden(lines){
        let l3_nums = 0     // 有多少活三
        let sl4_nums = 0    // 有多少冲四活四
        let ssr = 0         // 长连
        for (let i = 0; i < lines.length; i++){
            const a = lines[i]
            // 成五无视禁手 直接获胜
            if (a == Consts.UR){
                return RuleBase.WINNER_MOVE
            }else if (a == Consts.L3){
                l3_nums += 1
            } else if ((a == Consts.S4) || (a == Consts.L4)){
                sl4_nums += 1
            } else if (a == Consts.S44){
                sl4_nums += 2
            } else if (a == Consts.SSR){
                ssr += 1
            }
        }
        if ((ssr > 0) ||            // 长连
            (l3_nums > 1) ||        // 有两个以上双活三
            (sl4_nums > 1)){        // 有两个以上冲四/活四
            return RuleBase.FORBIDDEN_MOVE
        }
        return RuleBase.NORMAL_MOVE
    },
    white(lines){
        const max = Math.max(lines[0], lines[1], lines[2], lines[3])
        return ((max >= Consts.SSR) ? RuleBase.WINNER_MOVE : RuleBase.NORMAL_MOVE)
    },
}
const Rules = {}
Rules["无禁手"] = {
    use_forbidden: false,
    for_point: RuleBase.for_point,
    select_nums: RuleBase.select_nums,
    can_swap: RuleBase.can_swap,
}
Rules["仅禁手"] = {
    use_forbidden: true,
    for_point: RuleBase.for_point,
    select_nums: RuleBase.select_nums,
    can_swap: RuleBase.can_swap,
}
Rules["一手交换"] = {
    use_forbidden: false,
    for_point: RuleBase.for_point,
    select_nums: RuleBase.select_nums,
    // 下第二手棋前可交换
    can_swap: (step)=>(2 == step),
}
function make_3swap_5selectN(N){
    return {
        use_forbidden: true,
        for_point: RuleBase.for_point,
        // 第五手时打点
        select_nums: (step)=>((5 == step) ? N : 0),
        // 下第四手前可交换
        can_swap: (step)=>(4 == step),
    }
}
Rules["五手三打"] = make_3swap_5selectN(3)
Rules["五手五打"] = make_3swap_5selectN(5)

const PK = (()=>{
    const _board = Core.board_make()
    const _maxtimes = [0, 0]
    const _times_player = [[0, 0], [0, 0]]
    const _times_hasover = [false, false]
    let _timer_last_second = undefined
    let _next_pflag = undefined
    let _next_step = undefined
    let _selects = undefined
    const me = {}
    function to_player(pflag){
        return (pflag == Consts.P_BLACK) ? 0 : 1
    }
    me.reset = ()=>{
        _next_pflag = Consts.P_BLACK
        _next_step = 1
        _selects = []
        Core.board_clear(_board)
        return me
    }
    me.move = (loc)=>{
        _selects.forEach((i)=>{
            Core.board_set(_board, i, Consts.P_EMPTY)
        })
        if (_selects.length > 0){_selects = []}
        Core.board_set(_board, loc, _next_pflag)
        const player = to_player(_next_pflag)
        if (_times_hasover[player]){
            _times_player[player][1] = Consts.MAXTIME_STEP_FOR_OVERTOTAL
        }else{
            _times_player[player][1] = _maxtimes[1]
        }
        _next_pflag = Consts.P_swap(_next_pflag)
        _next_step += 1
        return me
    }
    me.unmove = (loc)=>{
        if (_next_step > 1){
            Core.board_set(_board, loc, Consts.P_EMPTY)
            _next_pflag = Consts.P_swap(_next_pflag)
            _next_step -= 1
        }
        return me
    }
    me.selects = ()=>_selects
    me.select_add = (loc)=>{
        Core.board_set(_board, loc, _next_pflag)
        _selects.push(loc)
        return me
    }
    me.select_in = (loc)=>{
        return R.any((i)=>(i == loc), _selects)
    }
    me.board = ()=>_board
    me.next_pflag = ()=>_next_pflag
    me.next_step = ()=>_next_step
    me.for_pieces = (f)=>{
        return Core.board_for_pieces(_board, f)
    }
    me.set_timelimit = (total, step)=>{
        _maxtimes[0] = total
        _maxtimes[1] = step
        _times_player[0][0] = total
        _times_player[0][1] = step
        _times_player[1][0] = total
        _times_player[1][1] = step
        _times_hasover[0] = false
        _times_hasover[1] = false
        return me
    }
    me.timer_start = ()=>{
        _timer_last_second = utils.clock.now_second()
        return me
    }
    me.timer_stop = ()=>{
        _timer_last_second = undefined
        return me
    }
    me.timer_has_start = ()=>(! R.isNil(_timer_last_second))
    me.timer_total = (pflag)=>{
        return _times_player[to_player(pflag)][0]
    }
    me.timer_step = (pflag)=>{
        return _times_player[to_player(pflag)][1]
    }
    me.timer_frame = ()=>{
        if (_timer_last_second != undefined){
            const now = utils.clock.now_second()
            const diff = now - _timer_last_second
            if (diff > 0){
                const player = to_player(_next_pflag)
                _times_player[player][0] = Math.max(_times_player[player][0] - diff, 0)
                if ((! _times_hasover[player]) && (_times_player[player][0] < 1)){
                    _times_hasover[player] = true
                }
                _times_player[player][1] = Math.max(_times_player[player][1] - diff, 0)
                _timer_last_second = now
            }
        }
        return me
    }
    return me
})()

export default {
    ...Consts,
    ...Core,
    RuleBase, Rules,
    PK,
    init(){},
}
